package com.nulldev.util.VariableAPI.util.random.fasterrng;

import java.io.Serializable;

public class MRG32k3a extends AbstractFasterRandom implements FasterRandom, Serializable {

	private static final long serialVersionUID = -1659680045363990971L;
	private static final long m1 = 4294967087L;
	private static final long m2 = 4294944443L;
	private static final long a12 = 1403580;
	private static final long a13 = 810728;
	private static final long a21 = 527612;
	private static final long a23 = 1370589;
	private static final long corr1 = (m1 * a13);
	private static final long corr2 = (m2 * a23);
	// This is equivalent to 2.328306549295727688e-10, but more precise as
	// there's no rounding involved.
	private static final double norm = 0x1.000000d00000bp-32;

	private long s10, s11, s12, s20, s21, s22;

	private static long staffordMix13(long z) {
		z = (z ^ (z >>> 30)) * 0xBF58476D1CE4E5B9L;
		z = (z ^ (z >>> 27)) * 0x94D049BB133111EBL;
		// This guarantees that we return a positive number
		return (z >>> 1) ^ (z >>> 32);
	}

	/** Constructs a random number generator. */
	public MRG32k3a() {
		this(Utils.getSeed());
	}

	/** Constructs a random number generator seeded by an {@code int}. */
	public MRG32k3a(int seed) {
		setSeed(seed);
	}

	/** Constructs a random number generator seeded by a {@code long}. */
	public MRG32k3a(long seed) {
		setSeed(seed);
	}

	/**
	 * Sets the state of this generator starting from a 64-bit seed.
	 *
	 * @param seed a 64-bit integer that will be used to seed a SplitMix64
	 *             generator, whose output will be used to seed this generator.
	 */
	@Override
	public void setSeed(long seed) {
		clearGaussian();
		s10 = staffordMix13(seed += 0x9e3779b97f4a7c15L) % m1;
		s11 = staffordMix13(seed += 0x9e3779b97f4a7c15L) % m1;
		s12 = staffordMix13(seed += 0x9e3779b97f4a7c15L) % m1;
		s20 = staffordMix13(seed += 0x9e3779b97f4a7c15L) % m2;
		s21 = staffordMix13(seed += 0x9e3779b97f4a7c15L) % m2;
		s22 = staffordMix13(seed += 0x9e3779b97f4a7c15L) % m2;
	}

	/**
	 * Set the state of this generator.
	 *
	 * @param seed six numbers: the first three values (s10, s11, s12; not all
	 *             zeros) must be in the range [0..4294967087) and the last three
	 *             values (s20, s21, s22; not all zeros) in the range
	 *             [0..4294944443).
	 */
	public void setSeed(final long... seed) {
		if (seed.length != 6)
			throw new IllegalArgumentException("You must provide six numbers");
		if (seed[0] == 0 && seed[1] == 0 && seed[2] == 0)
			throw new IllegalArgumentException("s10, s11 and s12 cannot be all zero");
		if (seed[3] == 0 && seed[4] == 0 && seed[5] == 0)
			throw new IllegalArgumentException("s20, s21 and s22 cannot be all zero");
		if (seed[0] < 0 || seed[1] < 0 || seed[2] < 0)
			throw new IllegalArgumentException("s10 (" + seed[0] + "), s11 (" + seed[1] + "), and s12 (" + seed[2] + ") cannot be negative");
		if (seed[3] < 0 || seed[4] < 0 || seed[5] < 0)
			throw new IllegalArgumentException("s20 (" + seed[3] + "), s21 (" + seed[4] + "), and s22 (" + seed[5] + ") cannot be negative");
		if (seed[0] >= m1 || seed[1] >= m1 || seed[2] >= m1)
			throw new IllegalArgumentException("s10 (" + seed[0] + "), s11 (" + seed[1] + "), and s12 (" + seed[2] + ") must be smaller than " + m1);
		if (seed[3] >= m2 || seed[4] >= m2 || seed[5] >= m2)
			throw new IllegalArgumentException("s20 (" + seed[3] + "), s21 (" + seed[4] + "), and s22 (" + seed[5] + ") must be smaller than " + m2);
		clearGaussian();
		s10 = seed[0];
		s11 = seed[1];
		s12 = seed[2];
		s20 = seed[3];
		s21 = seed[4];
		s22 = seed[5];
		// Throw away one value to align output with L'Ecuyer original version
		nextDouble();
	}

	/**
	 * Returns the next pseudorandom double in (0..1).
	 *
	 * @return the next pseudorandom double in (0..1).
	 */
	@Override
	public double nextDouble() {
		// Combination
		long r = s12 - s22;
		r -= m1 * ((r - 1) >> 63);

		// Component 1
		long p = (a12 * s11 - a13 * s10 + corr1) % m1;
		s10 = s11;
		s11 = s12;
		s12 = p;

		// Component 2
		p = (a21 * s22 - a23 * s20 + corr2) % m2;
		s20 = s21;
		s21 = s22;
		s22 = p;
		return r * norm;
	}

	@Override
	protected int next(final int bits) {
		// Combination
		long r = s12 - s22;
		r -= m1 * ((r - 1) >> 63);

		// Component 1
		long p = (a12 * s11 - a13 * s10 + corr1) % m1;
		s10 = s11;
		s11 = s12;
		s12 = p;

		// Component 2
		p = (a21 * s22 - a23 * s20 + corr2) % m2;
		s20 = s21;
		s21 = s22;
		s22 = p;
		return (int) (r >>> (32 - bits));
	}

}
